跳到主要内容

Java IO学习-File、Files、Path工具类

Java资源路径的问题

参考资料 Java 项目读取 resources 资源文件路径那点事

实际上可以通过类加载路径来取得资源加载路径

System.out.println(Temp.class.getResource(""));
// file:/D:/JavaProject/study-java/studySocket/target/classes/com/alsritter/
System.out.println(Temp.class.getClassLoader().getResource(""));
// file:/D:/JavaProject/study-java/studySocket/target/classes/

所以要读取项目内的文件,一般是通过获取类加载路径的方式来取得资源路径,其实可以直接取得路径,无需先调用类加载器

// 直接这样写就好了
InputStream fileReader = JdbcUtils.class.getResourceAsStream("/urlPath.properties");

class.getResourceAStream()class.getClassLoader().getResorceAsStream() 的区别

1) InputStream inStream = PropertiesTest.class.getResourceAsStream("test.properties");

​2) inStream = PropertiesTest.class.getResourceAsStream("/com/test/demo/test.properties")

3) inStream = PropertiesTest.class.getClassLoader().getResourceAsStream("com/test/demo/test.properties");


```java
// 之所以要封装一个方法无疑还是方便客户端的调用,省的每次获取 ClassLoader 才能加载资源文件的麻烦!
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}

所以直接 getResourceAsStream 实际上内部也是调用 getClassLoader().getResourceAsStream

加载资源 Demo

image.png

public class Temp {
public static void main(String[] args) throws IOException {

URL resource = Temp.class.getClassLoader().getResource("./temp.txt");
// file:/D:/JavaProject/study-java/studySocket/target/classes/temp.txt

try (FileInputStream fis = new FileInputStream(resource.getPath());
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr)) {

String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
}

File工具类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建,查找和删除操作;就是把电脑上的文件和文件夹封装成一个 File 类

注意 Linux 和 Windows 平台的分隔符不同

// 路径分隔符 Windows:使用的是;   Linux使用的是冒号:
String pathSeparator = File.pathSeparator;
// 文件名称分隔符 Windows:反斜杠\ Linux:正斜杠/
String separator = File.separator;
// 父路径和子路径可以单独的书写
String parent = "D:\\"
String child = "a\\b\\temp.txt"
File file = new File(parent,child);

获取文件信息

//返回此File的绝对路径名字符串。 
file.getAbsolutePath()
//返回由此File表示的文件或目录的名称。
file.getName()
//返回文件的大小(如果不存在则为0) 注意这有个坑,一般来说文件夹是没有大小的,但是有时会返回4096
//因为:文件夹也要记录本身的信息和自身下面的文件和文件夹的信息,4096是划分硬盘时最小扇区大小。
file.length()
//返回父路径
file.getParent()

判断功能方法

//判断此文件或者目录是否存在
file.exists();
//此File表示的是否为目录
file.isDirectory()
//此File是否为文件
file.isFile();

创建和删除

//返回值都是boolean

//当且仅当具有该名称的文件不存在时,创建一个新的空文件
file.createNewFile();
//删除由此File表示的文件或目录
file.delete();
//创建由此File表示的目录
file.mkdir();
//创建由此File表示的目录,包括任何必需但不存在的父目录
file.mkdirs();

目录的遍历

File file = new File("C:\\Users\\alsritter\\OneDrive\\MarkDown文本");

// 返回一个String数组,表示该File目录中的所有子文件或目录(注意不具有递归)
List<String> strings = Arrays.asList(file.list());
strings.forEach((e) -> {
System.out.println(e.toString());
});


// 返回一个File数组,表示该File目录中的所有子文件或目录(注意不具有递归)
List<File> files = Arrays.asList(file.listFiles());
files.forEach((e) -> {
System.out.println(e.toString());
});
public class FileText {
public static void main(String[] args) {
String path = "D:\\JAVA"; //要遍历的路径
File file = new File(path); //获取其file对象
func(file);
}

private static void func(File file){
File[] fs = file.listFiles();
for(File f:fs){
if(f.isDirectory()) //若是目录,则递归打印该目录下的文件
func(f);
if(f.isFile()) //若是文件,直接打印
System.out.println(f);
}
}
}

处理流异常

JDK7 的新特性,在 try 的后面可以直接定义流对象,这个流对象的作用域会在 try 中有效;try 中的代码执行完毕后会自动把流对象释放掉,不用写 finally

try(FileReader fileReader = new FileReader(file)) {
char[] buffer = new char[1024];
int len;
while ((len=fileReader.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
System.out.println("**************");
}
}catch (Exception e){
e.printStackTrace();
}

FilenameFilter

实现此接口的类实例可用于过滤器文件名

public class learn01 {
public static void main(String[] args) {
File file = new File("C:\\Users\\alsritter\\OneDrive\\MarkDown文本");

// 实例化一个文件名过滤器
FilenameFilter filter = new FilenameFilter() {
// 两个参数:dir:被找到的文件所在的目录 name:文件的名称
@Override
public boolean accept(File dir, String name) {
//endsWith方法,判断文件名后缀
return name.endsWith(".txt");
}
};

// 返回一个File数组,表示该File目录中的所有子文件或目录(注意不具有递归)
List<File> files = Arrays.asList(file.listFiles(filter));
for (File f : files) {
System.out.println(f);
}
}
}

FileFilter

这个用法同上差不多,只是这个给的是一个 完整的路径,不是像上面那样路径和名字分开

FileFilter fileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".txt") && pathname.isFile();
}
};

加载本地 class 文件

这里主要看 File 工具类如何取得本地文件的 file 协议地址

File协议主要用于访问本地计算机中的文件,就如同在 Windows 资源管理器中打开文件一样。 要使用 File 协议,基本的格式如下:file:///文件路径,比如要打开 F 盘的文件,那么可以在资源管理器或浏览器地址栏中输入:file:///f:/dir/temp.txt 在Linux 系统中,所有目录都是从根开始的。所以 file 协议后跟三个 /,其中最后一个 / 表示的就是根目录,不过在 Windows 里面也可以这么用。

/**
* 生成了编译好的字节码文件后需要将它加载到 JVM 里面
* 所以这里使用 URLClassLoader 这个加载器
* 使用 URLClassLoader 加载 Class,除了可以加载网络的 class 还可以加载本地 class(注意这个路径问题)
* 注意,这里无需写明具体的 $Proxy.class 路径,因为 urlClassLoader 加载的是目录下的 class 文件,所以只需目录
*
* @param path class 文件的目录
* @param filename 需要被加载的 class 文件名(类名)
* @param target 被代理的对象
* @return 返回生成的代理对象
*/
private static Object loadClassObject(String path, String filename, Object target) {
File clazzPath = new File(path);
Object object = null;
URL urls = null;
try {
urls = clazzPath.toURI().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}

System.out.println(urls); // file:/D:/JavaProject/studyALG/$Proxy.class

try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{urls})) {
// 与上面写的包名对应需要输入全类名
Class<?> loadClass = urlClassLoader.loadClass(filename);
object = loadClass.getConstructor(target.getClass().getInterfaces()[0]).newInstance(target);
} catch
(Exception e) {
e.printStackTrace();
}
return object;
}

Properties 文件

Properties 文件属性列表中的 每个键及其对应的值都是一个字符串

# setting.properties

key1=/data/hello.txt
key2=60

写入数据到文件

Properties 集合中的方法 store,把集合中的临时数据持久化写入到硬盘储存

//将此属性列表(键和元素对)写入此 Properties表中  
void store(OutputStream out, String comments)
//将此属性列表(键和元素对)写入此 Properties表中
void store(Writer writer, String comments)

//参数:
// OutputStream out:"字符输出流,不能写入中文"
// Writer writer:"字符输出流,可以写入中文"
// String comments:"注释,用来解释说明保存的文件是做什么用的"

注意!comments 不能使用中文,会产生乱码,默认是 Unicode 编码,所以一般使用空字符串代替

public class PropertiesTester extends TestCase {

public void writeProperties() {
Properties properties = new Properties();
OutputStream output = null;
try {
// 文件不一定是 properties 格式的,也可以是 txt 格式
output = new FileOutputStream("config.properties");
properties.setProperty("url", "jdbc:mysql://localhost:3306/");
properties.setProperty("username", "root");
properties.setProperty("password", "root");
properties.setProperty("database", "users");//保存键值对到内存
properties.store(output, "alsritter modify" + new Date().toString());
// 保存键值对到文件中
} catch (IOException io) {
io.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

读取数据

用法基本与上相同,只不过换成输入流

public class learn01 {

public static void main(String[] args){
String path = "C:\\Users\\alsritter\\Desktop";

//用来测试程序的效率,所以读取前后时间
long s = System.currentTimeMillis();


try(FileReader fileReader = new FileReader(path + "\\temp.txt")) {
Properties prop = new Properties();
prop.load(fileReader);
//指定读取的形式
System.out.println(prop.getProperty("key1"));
System.out.println(prop.getProperty("key3"));
System.out.println(prop.getProperty("key2"));

//或采用遍历的形式
prop.forEach((k,v)->{
System.out.println("key: " + k + " value:" + v);
});
} catch (IOException e) {
e.printStackTrace();
}

// 返回当前毫秒
long e = System.currentTimeMillis();
System.out.println("共耗时:"+(e - s)+"毫秒");
}
}

Path 与 Paths 工具类

  • Path 用来表示文件路径
  • Paths 是工具类,用来获取 Path 实例
Path source = Paths.get("1.txt"); // 相对路径 不带盘符 使用 user.dir 环境变量来定位 1.txt
Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了 d:\1.txt 反斜杠需要转义
Path source = Paths.get("d:/1.txt"); // 绝对路径 同样代表了 d:\1.txt
Path projects = Paths.get("d:\\data", "projects"); // 代表了 d:\data\projects
  • . 代表了当前路径
  • .. 代表了上一级路径

例如目录结构如下

d:
|- data
|- projects
|- a
|- b

代码:

Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径 会去除 . 以及 ..

输出结果为

d:\data\projects\a\..\b
d:\data\projects\b

Files 工具类

查找是否存在

Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));

创建一级目录

Path path = Paths.get("helloword/d1");
Files.createDirectory(path);

如果目录已存在,会抛异常 FileAlreadyExistsException 不能一次创建多级目录,否则会抛异常 NoSuchFileException

创建多级目录用

Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);

拷贝文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");

Files.copy(source, target);

如果文件已存在,会抛异常 FileAlreadyExistsException

如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制

Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

移动文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");

Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

这个 StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性

删除文件

Path target = Paths.get("helloword/target.txt");
Files.delete(target);

如果文件不存在,会抛异常 NoSuchFileException

删除目录

Path target = Paths.get("helloword/d1");
Files.delete(target);

如果目录还有内容,会抛异常 DirectoryNotEmptyException

遍历文件

可以使用 Files 工具类中的 walkFileTree(Path, FileVisitor) 方法,其中需要传入两个参数

  • Path:文件起始路径
  • FileVisitor:文件访问器,使用模板方法模式

接口的实现类 SimpleFileVisitor 有四个方法

  • preVisitDirectory:访问目录前的操作
  • visitFile:访问文件的操作
  • visitFileFailed:访问文件失败时的操作
  • postVisitDirectory:访问目录后的操作
public class TestWalkFileTree {
public static void main(String[] args) throws IOException {
Path path = Paths.get("F:\\JDK 8");
// 文件目录数目
AtomicInteger dirCount = new AtomicInteger();
// 文件数目
AtomicInteger fileCount = new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("===>"+dir);
// 增加文件目录数
dirCount.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);
// 增加文件数
fileCount.incrementAndGet();
return super.visitFile(file, attrs);
}
});
// 打印数目
System.out.println("文件目录数:"+dirCount.get());
System.out.println("文件数:"+fileCount.get());
}
}

运行结果如下

...
===>F:\JDK 8\lib\security\policy\unlimited
F:\JDK 8\lib\security\policy\unlimited\local_policy.jar
F:\JDK 8\lib\security\policy\unlimited\US_export_policy.jar
F:\JDK 8\lib\security\trusted.libraries
F:\JDK 8\lib\sound.properties
F:\JDK 8\lib\tzdb.dat
F:\JDK 8\lib\tzmappings
F:\JDK 8\LICENSE
F:\JDK 8\README.txt
F:\JDK 8\release
F:\JDK 8\THIRDPARTYLICENSEREADME-JAVAFX.txt
F:\JDK 8\THIRDPARTYLICENSEREADME.txt
F:\JDK 8\Welcome.html
文件目录数:23
文件数:279